#include "clientinstance.hh"

ClientInstance::ClientInstance()
{
    _run = false;
    _moduleName = "";
    _moduleHandle = 0;
}

ClientInstance::ClientInstance(Socket *network, clientCounter *counter)
{
    _run = false;
    _network = network;
    _counter = counter;
    _moduleName = "";
    _moduleHandle = 0;
}

ClientInstance::~ClientInstance()
{
    delete _network;
}

void ClientInstance::Start()
{
    setRunning(true);
    if (!(init() && createThread(&client_instance_starter, (void*)this)))
        cerr << "Compute Server cannot create main thread!" << endl;
}

void ClientInstance::Stop()
{
    setRunning(false);
    if (false == joinThread())
        destroyThread();
}

void ClientInstance::execute()
{
    PROTOCOL_CODES protoOrder;
    ADistributedModule *moduleInstance;

    _counter->AddClient();
    while (_counter->GetExecNumber() >= 5)
        usleep(1000);
    cout << "Client execution..." << endl;
    _counter->AddExec();
    if (!_client.SendConnectionConfirmation())
    {
        cerr << "SERVER: Cannot send connection confirmation. Closing client" << endl;
        setRunning(false);
    }
    while (IsRunning() && (protoOrder = (PROTOCOL_CODES)_client.RecvCode()))
    {
        switch (protoOrder)
        {
        case DATA:
            if (!_client.RecvData())
            {
                cerr << "SERVER: Error while receiving module" << endl;
                setRunning(false);
                break;
            }
            if (!saveModule())
            {
                cerr << "SERVER: Error while saving module. Closing client" << endl;
                setRunning(false);
                _client.SendData(false);
                break;
            }
            if (0 == (moduleInstance = getModuleInstance()))
            {
                cerr << "SERVER: Failed to load module. Closing client" << endl;
                setRunning(false);
                _client.SendData(false);
                break;
            }
            if (!executeModule(moduleInstance))
            {
                cerr << "SERVER: Cannot execute module. Closing client" << endl;
                setRunning(false);
                break;
            }
            if (!_client.SendData(true))
            {
                cerr << "SERVER: Cannot send module data. Closing client" << endl;
                setRunning(false);
                break;
            }

            delete moduleInstance;
            if (0 != dlclose(_moduleHandle))
            {
                cerr << "SERVER: Cannot unload module. Closing client" << endl;
                setRunning(false);
                break;
            }
            break;
        case PING:
            if (!_client.sendCode(_counter->GetClientNumber()))
            {
                cerr << "SERVER: Cannot send reply to ping request. Closing client" << endl;
                setRunning(false);
            }
            break;
        case CLOSE:
            cout << "SERVER: client finished it execution, closing connection!" << endl;
            setRunning(false);
            break;
        default:
            cerr << "SERVER: received an invalid code, closing client!" << endl;
            setRunning(false);
        }
    }
    _counter->RemoveExec();
    _counter->RemoveCLient();
    cout << "Client stopped!" << endl;
}

bool ClientInstance::IsRunning()
{
    bool tmp;

    lock();
    tmp = _run;
    unlock();

    return (tmp);
}

bool ClientInstance::init()
{
    _client.InitServer(_network);
    return (true);
}

void ClientInstance::setRunning(bool run)
{
    lock();
    _run = run;
    unlock();
}

bool ClientInstance::saveModule()
{
    void *code = 0;
    int codeSize;
    stringstream ss;
    FILE *module;

    ss << "/tmp/" << _client.GetId() << "_module.so";
    _moduleName = ss.str();
    _client.GetModuleCode(&code, &codeSize);
    cout << "SERVER: reveived module of size " << codeSize << endl;
    if (code == 0 || codeSize <= 0)
    {
        cerr << "SERVER: invalid module received!" << endl;
        return (false);
    }
    if (0 == (module = fopen(_moduleName.c_str(), "w+")))
    {
        cerr << "SERVER: cannot create module file!" << endl;
        return (false);
    }
    if ((unsigned int)codeSize != fwrite(code, 1, codeSize, module))
    {
        cerr << "SERVER: cannot write module file!" << endl;
        return (false);
    }
    if (0 != fclose(module))
    {
        cerr << "SERVER: cannot close module file!" << endl;
        return (false);
    }
    return (true);
}

ADistributedModule *ClientInstance::getModuleInstance()
{
    ADistributedModule *(*factory)() = 0;
    ADistributedModule *moduleInstance = 0;

    if (0 == (_moduleHandle = dlopen(_moduleName.c_str(), RTLD_NOW)))
    {
        cerr << "SERVER: " << dlerror() << endl;
        return (0);
    }
    if (0 == (*(void **) (&factory) = dlsym(_moduleHandle, /*maker symbol*/_client.GetModuleName().c_str())))
    {
        cerr << "SERVER: dlsym failed to find the function: " << _client.GetModuleName() << endl;
        cerr << "SERVER: " << dlerror() << endl;
        return (0);
    }
    moduleInstance = (*factory)();

    return (moduleInstance);
}

bool ClientInstance::executeModule(ADistributedModule *moduleInstance)
{
    void *data = 0;
    int dataSize = 0;

    _client.GetModuleData(&data, &dataSize);
    if (!(data == 0 && dataSize == 0) && !(data != 0 && dataSize > 0))
    {
        cerr << "SERVER: invalid module data received!" << endl;
        return (false);
    }
    if (!moduleInstance->LoadData(data))
    {
        cerr << "SERVER: cannot load module data!" << endl;
        return (false);
    }
    if (!moduleInstance->ExecuteLocal())
    {
        cerr << "SERVER: cannot execute module!" << endl;
        return (false);
    }
    if (!moduleInstance->ExportData(&data, &dataSize))
    {
        cerr << "SERVER: cannot export module data!" << endl;
        return (false);
    }
    _client.SetModuleData(data, dataSize);
    return (true);
}


void *client_instance_starter(void *data)
{
    ClientInstance *classInstance = reinterpret_cast<ClientInstance*>(data);
    classInstance->execute();
    return (data);
}
